package com.java.simple.study.io.nio;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

/**
 * @author zhoujunlin
 * @date 2023年04月19日 16:46
 * @desc
 */
@Slf4j
public class NIOServer {

    @SneakyThrows
    public static void main(String[] args) {
        // 创建服务端Socket通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 配置非阻塞方式
        serverSocketChannel.configureBlocking(false);
        // 设值端口9090
        serverSocketChannel.socket().bind(new InetSocketAddress(9090));
        // 多路复用器
        Selector selector = Selector.open();
        // serverSocketChannel注册到selector  并监听链接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            // 阻塞等待注册在Selector上的Channel中有事件发生 当Channel准备好I/O操作时（比如有新的客户端连接、有数据就绪可读取、可写通道可以写出数据等），Select方法会立刻返回
            // 这个方法返回的是关联通道已经准备好进行的I/O操作所对应的SelectionKey对象的数量。如果返回值为0，表示当前没有任何Channel准备好进行I/O操作
            selector.select();
            // 获得所有发生事件的channel的key 遍历key的集合并处理每一个key的channel
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                handle(selectionKey);
                // 删除本次处理的key  防止重复处理
                iterator.remove();
            }
            TimeUnit.MILLISECONDS.sleep(500);
        }

    }

    @SneakyThrows
    private static void handle(SelectionKey selectionKey) {
        if (selectionKey.isAcceptable()) {
            log.info("有客户端链接了");
            // 服务端监听客户端链接
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            // 处理链接请求  创建SocketChannel  方法阻塞  但是该方法时再链接事件发生后立即执行  所以相当于非阻塞
            // 接收完不需要阻塞等待客户端的读写操作
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            // 注册事件到Selector上  当读事件发生时  触发事件
            socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
        } else if (selectionKey.isReadable()) {
            log.info("有客户端向服务端写数据");
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            // 把数据读到buffer中  read方法本身会阻塞  但当read事件发生时 不会阻塞
            while (socketChannel.read(readBuffer) > 0) {
            }
            readBuffer.flip();
            String message = StandardCharsets.UTF_8.decode(readBuffer).toString();
            log.info("读到客户端发送的数据:{}", message);
            // 让key所关联的SocketChannel只对写操作感兴趣。
            selectionKey.interestOps(SelectionKey.OP_WRITE);
        } else if (selectionKey.isWritable()) {
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
            String response = "Message Received";
            writeBuffer.put(response.getBytes(StandardCharsets.UTF_8));
            writeBuffer.flip();
            socketChannel.write(writeBuffer);

            selectionKey.interestOps(SelectionKey.OP_READ);
        }

    }

}
